/*
 * Copyright (C) 2006 Apple Computer, Inc.  All rights reserved.
 * Copyright (C) 2006 Michael Emmel mike.emmel@gmail.com
 * All rights reserved.
 * Copyright (C) 2012, 2013 Sony Computer Entertainment Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GO188ODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#if !USE(NTF)

#ifndef ResourceHandleManager_h
#define ResourceHandleManager_h

#include "AuthenticationChallenge.h"
#include "FormDataStreamCurl.h"
#include "Frame.h"
#include "NetworkingContext.h"
#include "ProtectionSpace.h"
#include "ResourceHandleClient.h"
#include "ResourceRequest.h"
#include "Timer.h"
#include <wtf/Threading.h>
#include <wtf/threads/BinarySemaphore.h>

#if PLATFORM(WIN) || PLATFORM(MANX) && OS(WINDOWS)
#include <windows.h>
#include <winsock2.h>
#endif

#include <curl/curl.h>
#include <manx/NetworkProfile.h>
#include <manx/Ssl.h>
#include <manx/X509.h>
#include <wtf/Vector.h>
#include <wtf/text/CString.h>

namespace WebCore {

enum HTTPMethodType {
    HttpMethod_Get,
    HttpMethod_Head,
    HttpMethod_Post,
    HttpMethod_Put,
    HttpMethod_Delete,
    HttpMethod_Options,
};

enum HTTPState {
    HttpState_None                      = 0,
    HttpState_Cancelled                 = (1 << 0),
    HttpState_HasLocation               = (1 << 1),
    HttpState_RedirectedAfterPost       = (1 << 2),
    HttpState_IsPost                    = (1 << 3),
    HttpState_IsFinishedContext         = (1 << 4),
    HttpState_AuthCancelled             = (1 << 5),
    HttpState_HttpAuthInProcess         = (1 << 6),
    HttpState_HttpAuthModeFromHeader    = (1 << 7),
    HttpState_ProxyUserPassWasSet       = (1 << 8),
    HttpState_ProxyAuthInProcess        = (1 << 9),
    HttpState_ProxyAuthModeFromHeader   = (1 << 10),
    HttpState_HasRealmForHttpAuth       = (1 << 11),
    HttpState_HasRealmForProxyAuth      = (1 << 12),
    HttpState_DefersLoading             = (1 << 13),
    HttpState_IsHttps                   = (1 << 14),
    HttpState_IsSyncReq                 = (1 << 15),
    HttpState_HasHttpCredential         = (1 << 16),
    HttpState_HasProxyCredential        = (1 << 17),
};

class ResourceHandleContext : public Manx::Ssl::SslContext {
    WTF_MAKE_FAST_ALLOCATED;
public:

    ResourceHandleContext(ResourceHandle* job)
        : m_rawUrl(0)
        , m_handle(0) 
        , job(job)
        , m_customHeaders(0)
        , m_formDataStream(job)
        , m_curlRecvErrorRetryCount(0)
    {
        m_filteringhandle = 0;
        m_filteringCustomHeaders = 0;
        m_filteringHeadersSlist = 0;
        m_blockedLocation = false;
        m_curlResult = CURLE_OK;
        m_filteringActivatedStatus = 0;
        m_httpAuthType = Manx::HttpAuthNone;
        m_proxyAuthType = Manx::HttpAuthNone;
        m_httpState = HttpState_None;
        m_httpChallenge.nullify();
        m_proxyChallenge.nullify();
        if ("POST" == job->firstRequest().httpMethod())
            m_method = HttpMethod_Post;
        else if ("PUT" == job->firstRequest().httpMethod())
            m_method = HttpMethod_Put;
        else if ("HEAD" == job->firstRequest().httpMethod())
            m_method = HttpMethod_Head;
        else if ("DELETE" == job->firstRequest().httpMethod())
            m_method = HttpMethod_Delete;
        else if ("OPTIONS" == job->firstRequest().httpMethod())
            m_method = HttpMethod_Options;
        else 
            m_method = HttpMethod_Get;
    }

    ~ResourceHandleContext()
    {
        if (m_host)
            fastFree(m_host);
        if (m_rawUrl)
            fastFree(m_rawUrl);
        if (m_customHeaders) {
            curl_slist_free_all(m_customHeaders);
            m_customHeaders = 0;
        }
        if (m_filteringHeadersSlist) {
            curl_slist_free_all(m_filteringHeadersSlist);
            m_filteringHeadersSlist = 0;
        }
        if (m_httpState & HttpState_IsPost)
            Manx::NetworkProfile::unMountUploadDir();
    }

    char* m_rawUrl;
    CURL* m_handle;
    CURLcode m_curlResult;
    RefPtr<NetworkingContext> m_nwcontext;
    ResourceHandle* job;
    struct curl_slist* m_customHeaders;

    WebCore::HTTPMethodType m_method;
    unsigned m_httpState;

    // authenticate
    long m_httpAuthType; // Basic(1) or Digest(2) (set to curl_easy_setopt)
    long m_proxyAuthType; // Basic(1) or Digest(2) (set to curl_easy_setopt)
    String m_httpRealm;
    String m_proxyRealm;
    String m_httpUserPass;
    String m_proxyUserPass;
    String m_preSetProxyUserPass;
    ProtectionSpaceServerType m_httpProtectType;
    ProtectionSpaceServerType m_proxyProtectType;
    AuthenticationChallenge m_httpChallenge;
    AuthenticationChallenge m_proxyChallenge;
    ProtectionSpace m_httpSpace;
    ProtectionSpace m_proxySpace;

    FormDataStream m_formDataStream;
    RefPtr<FormData> m_formData;
    KURL m_KURL;
    KURL m_proxyKURL;

    Vector<char> m_postBytes;
    String m_respHeadersStr;

    // members for filtering
    String m_filteringurl;
    CURL* m_filteringhandle;
    struct curl_slist* m_filteringCustomHeaders;
    ResourceRequest m_filteringRequest;
    KURL m_evaluatedkurl;
    bool m_blockedLocation;
    int m_filteringActivatedStatus;
    struct curl_slist* m_filteringHeadersSlist;

    Vector<char> m_filteringRespHeaders;
    Vector<char> m_filteringBody;

    int m_curlRecvErrorRetryCount;
};

class ResourceHandleManager {
public:
#if PLATFORM(MANX)
    static ResourceHandleManager *s_sharedInstance;
    static void setup();
    static void cleanup();
#endif
    static ResourceHandleManager* sharedInstance();
    void add(ResourceHandle*, NetworkingContext*);
    void cancel(ResourceHandle*);
#if PLATFORM(MANX)
    void setCookies(const KURL&, const String&);
    String cookies(const KURL &);
    int deleteAllCookies();
    void loadCookies();
    void flushCookies();
    void cleanupSessionCookies();
    void saveCookies();
#endif

    void dispatchSynchronousJob(ResourceHandle*, NetworkingContext*);

    void setupPOST(ResourceHandle*, struct curl_slist**);
    void setupPUT(ResourceHandleContext*, struct curl_slist**);

    void setupPOSTAsync(ResourceHandleContext*, struct curl_slist**);
    void setupPUTAsync(ResourceHandleContext*, struct curl_slist**);

    static void signalWorkerThread()
    {
        WTF::MutexLocker lock(*s_workerThreadConditionMutex);
        s_workerThreadCondition->signal();
    }
    static void handleSslBadCertificate(void* context);
    static CURL* curlShareHandle()
    {
        return sharedInstance()->m_curlShareHandle;
    }

    static void lockCurlSharedResource(curl_lock_data);
    static void unlockCurlSharedResource(curl_lock_data);

    void initializeCurlHandle(CURL*, ResourceHandleContext*, curl_write_callback, curl_write_callback, curl_seek_callback);
    void initializeHandleForSync(ResourceHandleContext*);
    void initializeHandleForAsync(ResourceHandleContext*);
    void initializeHandleForFiltering(ResourceHandleContext*);
    void setupHttpMethod(ResourceHandleContext*);    
private:
    ResourceHandleManager();
    ~ResourceHandleManager();
    bool processDownload();
    bool processFiltering();

    static void saveCredential(ResourceHandleContext*, AuthenticationChallenge&, ProtectionSpace&, KURL&);

    static size_t prepareHandleAuthenticate(ResourceHandleContext*, long, long, size_t, void*);
    static size_t headerCallbackAsync(char*, size_t, size_t, void*);
    static size_t headerCallback(char*, size_t, size_t, void*);
    static size_t writeCallbackAsync(char*, size_t, size_t, void*);
    static size_t readCallbackAsync(void*, size_t, size_t, void*);

    static void postNotifyBadCertSynchronously(ResourceHandleContext*);
    static void notifyBadCertSynchronously(void* cmd);

    void removeFromCurl(ResourceHandleContext*);
    void removeFromCurlForFiltering(ResourceHandleContext*);

    bool removeScheduledJob(ResourceHandle*);
    void startJob(ResourceHandleContext*);
    void startFilteringJob(ResourceHandleContext*);
    void checkAndCreateFilteringThread();

    bool startScheduledJobs();
    bool startScheduledFilteringJobs();

    void saveAuthCredential(ResourceHandleContext*);
    void setCustomHeaders(const HTTPHeaderMap&, ResourceHandleContext*);

    CURLSH* m_curlShareHandle;
    char m_cookieJarFileName[256];
    char m_curlErrorBuffer[CURL_ERROR_SIZE];
    Vector<ResourceHandleContext*> m_resourceHandleContextList;
    Vector<ResourceHandleContext*> m_filteringResourceHandleContextList;

    static WTF::ThreadIdentifier s_verifySslThreadId;
    static WTF::ThreadIdentifier s_workerThreadId;
    static WTF::ThreadIdentifier s_filteringThreadId;

    static WTF::Mutex* s_verifySslThreadMutex;
    static WTF::Mutex* s_workerThreadMutex;
    static WTF::Mutex* s_filteringThreadMutex;
    static WTF::Mutex* s_verifySslThreadConditionMutex;
    static WTF::Mutex* s_workerThreadConditionMutex;
    static WTF::Mutex* s_filteringThreadConditionMutex;
    static WTF::Mutex* s_notifyBadCertConditionMutex;
    static WTF::Mutex* s_setupMutex;

    static WTF::ThreadCondition* s_verifySslThreadCondition;
    static WTF::ThreadCondition* s_workerThreadCondition;
    static WTF::ThreadCondition* s_filteringThreadCondition;
    static WTF::ThreadCondition* s_notifyBadCertCondition;

    static void createFilteringThread(WTF::ThreadFunction, void*, const char*);
    static void waitFilteringThread();

    static void verifySslThread(void*);
    static void workerThread(void*);
    static void waitWorkerThreadMessage(bool, long&);
    static void filteringThread(void*);
    static void handleBadCertificate(ResourceHandleContext*);

    static void selectSockets(CURLM* multi, long timeoutMilliSecond, int);
#if OS(ORBIS)
    static void selectSocketsForFiltering(CURLM* multi, long timeoutMilliSecond);
#endif

};

}

#endif
#endif // !USE(NTF)
